

/* ==================================================================================
 *
 * ORGANIZACION DE COMPUTADORAS (66.20)
 * Trabajo práctico Nº 0.
 * Integrantes:
 *            Monzon, Maximiliano (82341). maximilianohm@gmial.com 
 *            Toccalino, Elvio (). 
 *            , Mariano (). 
 *
   ================================================================================== */

# include "join.h"

//-----------------------------------------------------------------------------
/* print_help:
 *    Imprime la ayuda del comando join.
 * Predondicion: 
 *    el parametro program_name debe apunta a un string con el nombre del comando.
 * Postcondicion:
 *    se imprime por salida standard la ayuda del comando.
 */
void print_help (const char* program_name);

// ----------------------------------------------------------------------------
/* print_version:
 *    Imprime la version del comando join.
 * Predondicion: 
 *    el parametro program_name debe apunta a un string con el nombre del comando.
 * Postcondicion:
 *    se imprime por salida standard la version del comando.
 */

void print_version (const char* program_name);

// ----------------------------------------------------------------------------
/* prepare_files:
 *    Inicializa la entrada de datos del comando.
 * Parametros:
 *    path1: path al archivo maestro de entrada de datos.
 *    file1: referencia al archivo maestro de entrada de datos.
 *		archivo1_desde_stdin: indicará si el primer archivo es en realidad stdin.
 *    path2: path al archivo esclavo de entrada de datos.
 *    rile2: referencia al archivo esclavo de entrada de datos.
 * 		archivo2_desde_stdin: indicará si el segundo archivo es en realidad stdin.
 * Precondicion: 
 *    path1 no debe ser NULL.  
 * Postcondicion:
 *    Abre el archivo indicado por path1 y lo asigna a la referencia file1.  Si el path2 es NULL
 *    asocia la entrada estandar al la referencia del archivo esclavo.  Si path2 es no NULL entonces
 *    abre el archivo indicado por path2 y lo asigna a la referencia file2.
 * Retorno:
 * 1 si falló la apertura del primer archivo.
 * 2 si falló la apertura del segundo archivo.
 * 3 si el usuario pasó stdin para los dos archivos.
 * 0 para la salida correcta. */
int prepare_files(const char* path1, FILE** file1, unsigned short int* archivo1_desde_stdin
								 ,const char* path2, FILE** file2, unsigned short int* archivo2_desde_stdin);

// ----------------------------------------------------------------------------
/* request_memory:
 *    Inicializa un buffer de lectura y dos contenedores de líneas.
 * Postcondicion:
 *    Aloca KEY_SIZE cantidad de memoria para la key de line y VALUE_SIZE en cada contenedor de línea,
 *    y (KEY_SIZE + VALUE_SIZE) en el buffer de lectura.
 * Retorno:
 * cero si sale correctamente, distinto de cero en caso de error.
 */
int request_memory(line_t *line1, line_t* line2, char** linebuffer);

// ----------------------------------------------------------------------------
/* release_memory:
 *    Libera la memoria reservada durante la llamada a request_memory.
 * Precondicion: 
 *    los mismos argumentos participaron de una llamada previa a request_memory.
 * Postcondicion:
 *    la memoria en los argumentos es liberada.
 */
void release_memory(line_t *line1, line_t* line2, char* buffer);

// ----------------------------------------------------------------------------
/* read_line:
 *    Lee una linea del archivo.
 * Parametros:
 *    file: referncia al archivo de donde se lee la linea.
 *    line: referencia a la linea utilizada para la lectura del archivo.
 *		reading_from_stdin: un flag para avisar que se lee desde la entrada estandar.
 * Precondicion: 
 *    No se debe haber alcanzado EOF.
 * Postcondicion:
 *    line contiene la informacion correspondiente a una linea leida del archivo.
 * Retorno:
 *    Retorna un flag de tipo entero indicando el resultado de la operacion.  ERROR_UP
 *    en caso de haber ocurrido un erro de lectura. JOIN_STDIN_DONE si se terminó
 *    de leer de la entrada estandar. 0 caso contrario.
 */
int read_line(FILE* file, line_t *line, char* linebuffer, const unsigned short int reading_from_stdin);

// ----------------------------------------------------------------------------
/* fn_cmp_case_sensitive:
 *    Compara dos keys en modo distincion entre mayusculas y minusculas.
 * Parametros:
 *    key1: clave a comparar.
 *    key2: clave a comparar.
 * Precondicion: 
 *    key1 no debe ser NULL.  key2 no debe ser NULL.
 * Retorno:
 *    Retorna 0 si las keys son iguales. 1 caso contrario.
 */

int fn_cmp_case_sensitive(char* key1, char* key2);

// ----------------------------------------------------------------------------
/* fn_cmp_case_insensitive:
 *    Compara dos keys en modo NO distincion entre mayusculas y minusculas.
 * Parametros:
 *    key1: clave a comparar.
 *    key2: clave a comparar.
 * Precondicion: 
 *    key1 no debe ser NULL.  key2 no debe ser NULL.
 * Retorno:
 *    Retorna 0 si las keys son iguales. 1 caso contrario.
 */

int fn_cmp_case_insensitive(char* key1, char* key2);

// ============================================================================

// FUNCION MAIN.

// ============================================================================

int main (int argc, char** argv) {

  // Flags operacionales.
  //
  unsigned short int
      version = 0
    , help = 0
    , case_insensitive = 0
    , line_found = 0
    , archivo1_desde_stdin = 0
    , archivo2_desde_stdin = 0
    ;
  
  // Control de archivos.
  //
  char 
      *inputpath1 = NULL   // path del archivo maestro.
    , *inputpath2 = NULL   // path del archivo esclavo.
    , *linebuffer = NULL	 // buffer de lectura de líneas.
    ;
  FILE 
      *file1 = NULL  // Referencia al archivo maestro.
    , *file2 = NULL  // Referencia al archivo esclavo.
    ;
  
    line_t 
        line1 = { NULL, NULL }   // Linea del archivo maestro.
      , line2 = { NULL, NULL }   // Linea del archivo esclavo.
      ;
  
  // Declaracion de la funcion de comparacion de claves.
  //
  fn_cmp cmp_function = NULL;

  int ret = 0;
  
  //Lectura de argumentos.
  //
  ret = handle_args(argc, argv, &inputpath1, &inputpath2, &version, &help, &case_insensitive);
  
  switch (ret) {
    case HANDLE_ARGS_INSUFFICIENT_OPT :
    case HANDLE_ARGS_INVALID_OPT : 
    case HANDLE_ARGS_WRONG_NUM_ARGS : help = 1; break;
    
    case HANDLE_ARGS_DONE : break;

    case HANDLE_ARGS_FATAL_ERROR :
    default : return EXIT_FAILURE;
    
  }
  
  // En caso de comando version habilitado imprimo la version.
  //
  if (version) {
    print_version(argv[0]);
    return EXIT_SUCCESS;
  }
  
  // En caso de comando ayuda habilidado imprimo la ayuda.
  //
  if (help) {
    print_help (argv[0]);
    return EXIT_SUCCESS;
  }
  
  // Definicion de la funcion de comparacion en funcion del comando ignore-case.
  //
  cmp_function = &fn_cmp_case_sensitive;
  
  if (case_insensitive) 
    cmp_function = &fn_cmp_case_insensitive;
  
  // Definicion de las entradas de datos.
  //
  if (ret = prepare_files(inputpath1, &file1, &archivo1_desde_stdin, inputpath2, &file2, &archivo2_desde_stdin)) {
    if (ret != 3) fprintf(stderr,"Error. Los archivos (alguno de ellos) no pudo ser abierto para lectura.\n");
   	else print_help(argv[0]);
    return EXIT_FAILURE;
  }

  if (request_memory(&line1, &line2, &linebuffer)) {
    // No hay memoria suficiente para operar. Informo la situacion y salgo.
    //
    printf("\nerror: memoria insuficiente.\n");
    return EXIT_FAILURE;
  }
  
  // Join de los datos de entrada en funcion del campo clave.
  //
  while (! feof(file1)) {
  
    if (ret = read_line(file1, &line1, linebuffer, archivo1_desde_stdin)) {
    
      switch(ret) {
        case ERROR_MEMORY: fprintf(stderr, "Error. Fallo al pedir memoria.\n"); break;
        case ERROR_LENGTH: fprintf(stderr, "Error. Línea de primer archivo demasiado larga.\n"); break;
        case ERROR_UP:     fprintf(stderr, "Error. Primer archivo con formato incorrecto.\n"); break;
        case ERROR_IO:		 continue; /* la lectura falla por EOF. */
        case JOIN_STDIN_DONE: break;
        default: break;
      }
    
      fclose(file1); fclose(file2);
      release_memory(&line1,&line2,linebuffer);
      return (ret == JOIN_STDIN_DONE) ? EXIT_SUCCESS : EXIT_FAILURE;
     
    }

    line_found = 0;
    while (! line_found) {
    
      /* es un error que haya una línea en el archivo 1 que no exista en el 2. */
      if (feof(file2)) {
        release_memory(&line1, &line2, linebuffer);
        fclose(file2); fclose(file1);
        fprintf(stderr, "Error. Una clave del primer archivo no estaba presente en el segundo.\n");
        return EXIT_FAILURE;
      }

      if (ret = read_line(file2, &line2, linebuffer, archivo2_desde_stdin)) {
        switch(ret) {
		      case ERROR_MEMORY: fprintf(stderr, "Error. Fallo al pedir memoria.\n"); break;
		      case ERROR_LENGTH: fprintf(stderr, "Error. Línea de segundo archivo demasiado larga.\n"); break;
		      case ERROR_IO:		 continue; /* la lectura falla por EOF. */
		      case JOIN_STDIN_DONE: break;
		      
		      /* por convención, salteamos las líneas del segundo archivo que tengan mal formato. */
		      case ERROR_UP:     fprintf(stderr, "Error. Segundo archivo con formato incorrecto.\n"); break;
          default: break;
        }
      
        fclose(file1); fclose(file2);
        release_memory(&line1, &line2, linebuffer);
         
        return EXIT_FAILURE;
      }

      if ((*cmp_function)(line1.key, line2.key)) {
        fprintf(stdout, "%s %s %s\n", line1.key, line1.value, line2.value);
        line_found++;
      }
      
    } //while
  } // while
  
  fclose(file1); 
  fclose(file2);
  release_memory(&line1, &line2, linebuffer);
  
  return EXIT_SUCCESS;
  
} // main

// ============================================================================

// DEFINICION DE FUNCIONES.

// ============================================================================

void print_help (const char* program_name) {
  printf("Usage: %s [OPTION]... FILE1 FILE2\n\nFor each pair of input lines with identical join fields, write a line to\nstandard output. The default join field is the first, delimited\nby whitespace. When FILE1 or FILE2 (but not both) is -, read standard input.\n\nOPTIONS:\n-i, --ignore-case  ignore differences in case when comparing fields\n-h, --help         display this help and exit\n-v, --version      output version information and exit\n", program_name);
}
// ----------------------------------------------------------------------------

void print_version (const char* program_name) {
	printf ("%s - version 0.3\nLicense GPLv3: GNU GPL version 3\nWritten by Maximiliano Monzón, Elvio Toccalino y Mariano Ribotta.\n", program_name);  
}

// ----------------------------------------------------------------------------

int prepare_files(const char* path1, FILE** file1, unsigned short int* archivo1_desde_stdin
								 ,const char* path2, FILE** file2, unsigned short int* archivo2_desde_stdin) {
	unsigned short int stdin_in_use = 0;
  
  if (0 == strcmp("-", path1)) {
  	stdin_in_use++;
  	*archivo1_desde_stdin = 1;
  	*file1 = stdin;
  }
  else if (NULL == (*file1 = fopen(path1, "r"))) {
    perror("prepare_files() (first file path)");
    return 1;
 	}

  if (0 == strcmp("-", path2)) {
  	if (!stdin_in_use) {
  		*file2=stdin;
  		*archivo2_desde_stdin = 1;
  	}
  	else return 3; // el vivo quiso usar stdin para las dos entradas.
  }
  else if (NULL == (*file2 = fopen(path2, "r"))) {
    perror("prepare_files() (second file path)");
    fclose(*file1);
    return 2;
  }
  return 0;
}

// ----------------------------------------------------------------------------

int request_memory(line_t *line1, line_t *line2, char **linebuffer) {

	if (NULL == (*linebuffer = (char*) calloc (KEY_SIZE + VALUE_SIZE, sizeof(char))))
		return 1;
	  
  if (NULL == (line1->key = (char*)calloc(KEY_SIZE, sizeof(char)))) {
  	free(*linebuffer);
    return 1;
  }
  if (NULL == (line1->value = (char*) calloc(VALUE_SIZE, sizeof(char)))) {
    free(*linebuffer);
    free(line1->key);
    return 1;
  }

  if (NULL == (line2->key = (char*)calloc(KEY_SIZE, sizeof(char)))) {
  	free(*linebuffer);
  	free(line1->key); free(line1->value);
    return 1;
  }
  if (NULL == (line2->value = (char*) calloc(VALUE_SIZE, sizeof(char)))) {
  	free(*linebuffer);
  	free(line1->key); free(line1->value);
  	free(line2->key);
    return 1;
  }
  return 0;
}

// ----------------------------------------------------------------------------

void release_memory(line_t *line1, line_t *line2, char* linebuffer) {
  free(line1->key); free(line1->value);
  free(line2->key); free(line2->value);
	free(linebuffer); 
}

// ----------------------------------------------------------------------------

int read_line(FILE* file, line_t *line, char* linebuffer, const unsigned short int reading_from_stdin) {

	if (NULL == fgets(linebuffer,	KEY_SIZE + VALUE_SIZE, file))
		return ERROR_IO;

	/* si la línea se leyó desde stdin y fue una línea vacía, leo una segunda... */
	if (reading_from_stdin && !strcmp("\n", linebuffer)) {
	
		if (NULL == fgets(linebuffer,	KEY_SIZE + VALUE_SIZE, file)) return ERROR_UP;
		if (!strcmp("\n", linebuffer)) return JOIN_STDIN_DONE;
	}

  if (2 != sscanf(linebuffer, "%s %[0-9a-zA-ZñüÑÜáéóíú.,;:~`<>=¬_-\\/¿?!¡'}{()°|@#$%&*+´^ \t\"]\n", line->key, line->value))
    return ERROR_UP;
  return 0;
}

// ----------------------------------------------------------------------------

int fn_cmp_case_sensitive(char* key1, char* key2) {
  int ret = strcmp(key1, key2);
  return (ret == 0) ? 1 : 0;
}

// ----------------------------------------------------------------------------

int fn_cmp_case_insensitive(char* key1, char* key2) {
  int c1, c2, i, n;
  if ((n = strlen(key1)) != strlen(key2)) return 0;
  
  for (i = 0; i < n; i++) {
    c1 = key1[i]; c2 = key2[i];
    if (tolower(c1) != tolower(c2)) return 0;
  }
  return 1;
}

